1. Field of the Invention
The present invention relates generally to computer system security, integrity, and reliability. More particularly, the present invention relates to examination and certification of software application programs to identify and eliminate vulnerabilities arising from poor software application programming techniques.
2. Background of the Invention
The explosion of electronic commerce has placed computer software applications at the cornerstone position of on-line business. Software is the brick and mortar of the new economy, but the migration from physical to virtual retail space has placed both the consumer and vendor at risk in unforeseen ways. If the new economy is going to survive, software will have to become more resistant to attack and will have to continuously improve to meet the rigorous demands of an on-line market.
An example of the magnitude of the problems faced by software users is illustrated by the distributed denial-of-service (dDoS) attacks against major e-commerce sites of February, 2000. Some of the brightest luminaries in e-commerce, including Yahoo!, Amazon.com, Buy.com, ZDNet, and CNN.com were effectively taken down for a period of hours by these attacks. What is most impressive and disturbing about these attacks is that they were against very high volume sites. For instance, according to Media Metrix, an online traffic measurement firm, Yahoo! had more unique visitors in January 2000 than any other online site. The other victims were among the top fifty sites. The dDoS attacks were able to bombard these sites with data at rates of up to one gigabit of data per second. The collective downtime of these sites resulted in a loss of revenue estimated to be in the millions of U.S. dollars.
Though denial-of-service (DoS) attacks often exploit weaknesses in protocols to hold network services hostage to the attacks, what is often overlooked by analysts is that such dDoS attacks are often made possible by flaws in software. A key to implementing an effective dDoS attack is to compromise a very large number of machines in order to plant the dDoS attack software, which, in the February attacks, went by names such as Trinoo or TFN2000. Sites are usually compromised in the first place by exploiting some flaw in software. In the case of many dDoS attacks, Unix servers are compromised, often by a well-known flaw in the Remote Procedure Call (RPC) service. Once a site is compromised and the malicious software installed, the compromised site becomes a zombie that acts maliciously on command at some future time. One of the keys to preventing these types of attacks in the future is to secure the software on the server systems such that they are not vulnerable to compromise in the first place.
In any e-commerce site, the software that runs the site is business-critical by definition. The failure of the software under any conditions, including normal operation, as well as unusual or attack conditions, can result in immediate loss of revenue, as well as jeopardizing the long-term viability of the business. For instance, well-known flaws in CGI scripts have enabled hackers to alter Web pages with political messages. If the Web pages of a financial investment firm were vandalized, investors and Wall Street would likely lose confidence in the ability of the firm to securely manage the assets of firm's investors.
For companies that develop and release application software, the expense in adequately addressing security vulnerabilities is very high. Moreover, for any vulnerabilities that were not adequately foreseen, there will be a corresponding drop in consumer confidence which cannot be measured. For example, both Netscape and Microsoft experienced well-publicized security-related flaws in their Internet browsers in 1997.
Developers of operating systems such as Sun Microsystems and Hewlett-Packard also spend considerable human resources tracking bugs that have the potential to be security flaws in their commercial operating systems. Such costs are often transferred to the end users, either directly (in the form of increased software prices) and indirectly (in the form of increased maintenance costs). It is well-known that the time and expense involved for system administrators to patch, upgrade, and maintain the security of computer systems is very high and increases with both new software additions and more sophisticated attacks.
The buffer overrun attack is one of the most pervasive modes of attack against computer systems today. Probably the most infamous buffer overrun attack is the Morris worm of 1988 that resulted in the shutdown of a significant portion of the Internet infrastructure at the time (which consisted of primarily university and government nodes). The worm was a self-propagating buffer overrun attack that exploited a program vulnerability in the Unix fingerd network service. The worm illustrated the serious nature of software flaws and how they can be leveraged to breach security on other systems.
Since the Morris worm, buffer overrun attacks have become a very popular method of breaking into systems or obtaining super user privilege from user-level accounts. According to statistics released by the Computer Emergency Response Team (CERT) Coordination Center of Carnegie Mellon University's Software Engineering Institute, about 50 percent of computer incidents reported today in the field involve some form of buffer overrun.
To further complicate the problems presented with application software, unsafe languages, such as C, make buffer overflow attacks possible by including standard functions, such as, for example, gets, strcat, and strcpy, that do not check the length of the buffer into which input is being copied. If the length of the input is greater than the length of the buffer into which it is being copied, then a buffer overflow can result. Safe programming practices that allow only constrained input can prevent a vast majority of buffer overflow attacks. However, many security-critical programs already in the field today do not employ safe programming practices. In addition, many of these programs are still coded in commercial software development labs in unsafe languages today.
As described above, buffer overrun attacks are made possible by program code that does not properly check the size of input data. When input is read into a buffer and the length of the input is not limited to the length of the buffer allocated in memory, it is possible to run past the buffer into critical portions of the stack frame. Overrunning the buffer results in writing to memory that is not reserved exclusively for the buffer. The consequences of overrunning a buffer can range from no discernible effect to failure of the program execution, and even to execution of machine instructions contained in the input. If the unconstrained input can write over specific data in the program stack frame, then it may be possible to execute arbitrary program code included in the input.
The stack frame is the part of a process' address space that is used to keep track of local function data when a function is called. When calling a function, a new stack frame is created for the function that is called. The calling function “pushes” the address of the next instruction to be executed after returning from the called function on this stack. This address is known as the return instruction pointer. After the program finishes executing the called function, a pointer to the next instruction to be executed is “popped off” the stack. The value of the operation code (opeode) pointed to by the instruction pointer is loaded and that instruction is executed.
By overwriting a buffer allocated on the stack, it is possible to change the instruction pointer to point to another address. In the case of many program crashes caused by buffer overruns, the instruction pointer is overwritten with random or garbage data that does not correspond to a legitimate instruction address. Upon returning from the called function, the processor attempts to execute an invalid instruction and an exception is generated. In this case, the program will normally abort execution, usually (but not always) without serious consequence on security, safety or integrity.
On the other hand, if the input stream that overruns the buffer is carefully crafted, it is possible that the instruction pointer can be overwritten in a principled manner. That is, a specific address can be written into the instruction pointer so that when it is evaluated, the next instruction to be executed is located at an address in the stack frame. With the address pointing back into the stack, it is possible to execute any instructions embedded in the input stream that have been written into the stack.
The process to implement the buffer overrun is commonly known as “smashing the stack.” An exemplary process for smashing the stack or overrunning buffers is illustrated in FIGS. 1A and 1B. Program 10 includes the function “MAIN” which defines array variable 12 (illustrated in FIG. 1B) with the label “LARGE.” Array LARGE is defined with a length of two thousand bytes. After creating the array LARGE, function MAIN fills it with two thousands “X” characters, as shown in FIG. 1B. Next, function MAIN calls the function “OVERFLOW” with a pointer to array “LARGE” passed as an argument. In OVERFLOW, array variable 14, labeled “SMALL,” is defined with a length of one hundred bytes (illustrated in FIG. 1C). The left side of FIG. 1D shows program stack 20 illustrating how memory is allocated when the OVERFLOW function is called in program 10. As shown in FIG. 1D, the array SMALL is allocated one hundred bytes, represented by block 22. After SMALL, program stack 20 has memory reserved for the stack frame pointer (SFP) in block 24, the return instruction pointer (IP) in block 26, and the pointer (in block 28) that was pushed onto the stack when OVERFLOW was called. After creating array SMALL, the OVERFLOW function simply copies the contents of the array LARGE into the memory reserved for array SMALL using C's strcpy function.
Unfortunately, strcpy does not check the length of the source variable before copying it to the destination variable. As a result, the two thousand “X” characters are written into the one hundred character long array (block 22) and into the adjoining memory locations as shown in the right side of FIG. 1D. That is, after the first one hundred Xs are copied, the remaining nineteen hundred characters will overwrite the SFP, the return IP, and even the pointer.
After the OVERFLOW function finishes executing, the processor will pop off the return IP address and execute the instruction located at that address. In this example, the address pointed to by the integer value of X . . . X (length of pointer will depend on the system architecture) is probably not an instruction, and as a result, program 10 will probably crash. However array LARGE could have been intelligently loaded with input that places a meaningful address at the return IP location. After returning from the OVERFLOW function, the next instruction that will execute will be whatever instruction is stored in the address stored in the return IP location. If the attacker inserts instructions (i.e. code) into another location within the overrun buffer (i.e., boxes 22-28 or beyond), then the attacker could also insure the return IP location (box 226) then points to the location of his code and will be able to execute code of his own choice.
This technique is as effective as being able to access and modify the program source code, recompile it, and execute it without ever having access to the local source code. Smashing the stack is one of the primary attacks launched against SUID root programs, i.e., programs that run as the super user on UNIX-based systems. The problem illustrated in FIGS. 1A-1D was that a programming error allowed a large buffer to overwrite a smaller buffer. In the Figure it may seem fairly apparent that this would happen, but in many programs, the programmer is assuming that the user will input values well within the buffers allocated. However, no provision is made to handle input from a malicious or even careless user. The exploit was made possible in this case because the programmer used the strcpy function instead of some other function that would have performed bounds checking to prevent the data from being overwritten.
In the wake of many well-publicized online failures, such as those described herein, as well as failures of online trading firms to meet customer demands at critical times, one or more government agencies or other self-governing bodies may well institute new requirements on public firms whose financial health depends on their information technology (IT) systems. For example, regulatory bodies, such as the U.S. Securities and Exchange Commission (SEC), could require publicly-traded companies to issue audited statements on the ability of their IT systems to withstand unusual conditions, such as volatile market conditions, high consumer demand, as well as malicious attack.
Software certification is the process of analyzing software to determine whether or not the risks posed by the software are acceptable for a business critical environment. It is an assessment process that is acceptable in a business paradigm where software vendors can obtain independent, third party assurance that the vendor's software is reliable, safe, or secure in a given application.
To date, certification is largely performed on people and processes. Thus, a particular organization may be certified to produce commercial grade software by meeting an industry accepted standard for process maturity, such as the Software Engineering Institute's Capability Maturity Model (SEI-CMM) or International Standards Organization (ISO) 9000 standards. Similarly, individuals may be certified to work on particular hardware and software platforms. Examples are obtaining the Microsoft Certified Systems Engineer (MCSE) certification or obtaining Professional Engineer (PE) certification in a particular State to practice engineering. These certification processes are designed to encourage higher quality work and to provide some level of assurance to end users that the individual or organization is qualified to perform the job for which they are certified. However, none of these certifications actually certify the end product that results from the job.
Under existing certification programs, an end user may have some level of assurance that a particular individual or organization is qualified to produce quality software. However, there is little or no assurance that the resulting software is of high quality. Even SEI-CMM level 4 organizations (a very mature level on the SEI-CMM scale) can produce shoddy software, while same individuals with no explicit software engineering process can produce high quality software. Certifications of people and processes provide a good indicator for the expected quality of software but do not speak to the actual quality of the finished product.
In addition to certification of processes and people, a need exists for certification processes that certify the actual quality of software. Two major problems face software users and consumers alike: (1) a lack of sound metrics for quantifying that information systems are trustworthy, and (2) the absence of an organization (such as an Underwriter's Laboratory) to apply the metrics in order to assess trustworthiness. In fact, if such problems were solved, software vendors who sought to provide reliable products would also benefit due to a higher premium or larger customer-based that should result from the increased consumer confidence in the vendors' wares.
A number of different efforts have focused on identifying vulnerabilities in software or protecting systems from software vulnerabilities. For example, as an alternative to source code-based analysis, StackGuard, a gcc compiler variant for Linux developed by the Oregon Graduate Institute, attempts to protect buffers from stack smashing attacks by aborting the program if the return address pushed on the stack is overwritten. StackGuard can prevent stack smashing attacks from running arbitrary code embedded in user input, but will not protect programs against all buffer overflow attacks. For example, buffer overflow attacks that overwrite local variables that were never intended to be changeable by a user can result in security violations not prevented by StackGuard.
The Fuzz tool is another tool that can be used to identify and prevent overflow buffers, but it may produce inconclusive results. Because input is randomly generated, the vulnerability of the program executing user-defined code cannot fully be assessed. Similarly, the FIST tool implements specific fault injection functions that determine the program's vulnerability to specially-crafted buffer overflow attacks, but cannot protect against all buffer attacks.
Various run-time approaches have also been addressed. One such approach is dynamic interception of buffer overflows by using the libsafe library. While dynamic techniques can offer some level of run-time protection, they often incur a performance penalty. Dynamic approaches are also limited to buffer-overflow specific attacks and can make no claims about the overall security of the software. While a dynamic approach can offer a great safety-net, a static-analysis of the source code is preferable to assure that code is written in a safe and proper manner.
Another approach that has been examined is the ITS4 Security Scanner. ITS4 is a simple tool that statically scans C and C++ source code for potential security vulnerabilities. It is a command-line tool that works across Unix environments. ITS4 scans source code, looking for function calls that are potentially dangerous. ITS4 is not sufficient for identifying potential vulnerabilities because it is not context- or flow-sensitive and thus it is not possible for it to reduce the large number of potential vulnerability locations it identifies.
Another approach has been examined in the recent work in the static detection of buffer overflow vulnerabilities by David Wagner et al. of University of California, Berkeley. Wagner attempts to place provably maximal hounds on the properties of the arguments passed to library calls. Then, using that information one can determine whether or not certain heuristic policies have be violated. In the case of buffer overflows, Wagner reduces strings to be represented by two variables; all other information is abstracted away. This simple string representation allows an analyst to only focus on the string properties most important in detecting buffer overrun problems. The constraint language developed by Wagner is very powerful, but the scanning techniques taken in his approach are overly simplified and still lead to a large number of false positives.
Accordingly, a need therefore exists for systems and methods for providing certification for any essential software system, where the failure of that software may result in unacceptable losses, financial or otherwise.